﻿using gt.AspNetCore.Authentication.Basic.Events;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace gt.AspNetCore.Authentication.Basic
{
    public class BasicHandler : AuthenticationHandler<BasicOption>
    {
        private const string KEY_AUTHORIZATION = "authorization";
        private const string KEY_SPLIT = ":";

        protected new BasicEvents Events
        {
            get => (BasicEvents)base.Events;
            set => base.Events = value;
        }

        public BasicHandler(IOptionsMonitor<BasicOption> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            string authorization = Request.Headers[KEY_AUTHORIZATION];
            if (string.IsNullOrEmpty(authorization))
            {
                Logger.LogInformation("请求头authorization为空，目标路径{0}", Request.Path);
                return AuthenticateResult.NoResult();
            }
            string token = string.Empty;
            if (authorization.StartsWith(BasicDefault.AuthenticationScheme + " ", StringComparison.CurrentCultureIgnoreCase))
            {
                token = authorization.Substring(BasicDefault.AuthenticationScheme.Length).Trim();
            }
            if (string.IsNullOrEmpty(token))
            {
                Logger.LogInformation("无效的请求头authorization，目标路径{0}", Request.Path);
                return AuthenticateResult.NoResult();
            }

            var checkUser = Options.ValidateUser;
            if (checkUser == null)
            {
                Logger.LogInformation("Basic TokenValidator不能，目标路径{0}", Request.Path);
                return await Task.FromResult(AuthenticateResult.NoResult());
            }

            try
            {
                var data = Encoding.UTF8.GetString(Convert.FromBase64String(token));
                if (string.IsNullOrEmpty(data)) throw new Exception("basic token 格式错误");

                string[] array = data.Split(KEY_SPLIT.ToCharArray());
                if (array.Length != 2) throw new Exception("basic token 格式错误");

                var username = array[0];
                var password = array[1];
                if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) throw new Exception("basic token 格式错误");

                if (!checkUser(username, password))
                {
                    Logger.LogInformation("token 验证失败");
                    return AuthenticateResult.Fail("token 验证失败");
                }

                var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.Name, username)
                };

                var principer = new ClaimsPrincipal(new ClaimsIdentity(claims, BasicDefault.AuthenticationScheme));
                var validatedContext = new BasicTokenValidatedContext(Context, Scheme, Options)
                {
                    Principal = principer
                };

                await Events.TokenValidated(validatedContext);

                validatedContext.Success();

                return validatedContext.Result;
            }
            catch (Exception ex)
            {
                Logger.LogDebug(token + " validate failed: " + ex.Message);
                return AuthenticateResult.Fail(ex.Message);
            }

        }

        protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
        {
            var authResult = await HandleAuthenticateOnceSafeAsync();

            Response.Headers.Add(HeaderNames.WWWAuthenticate, BasicDefault.AuthenticationScheme);
            Response.StatusCode = 401;
            if (authResult.Failure != null && !string.IsNullOrEmpty(authResult.Failure.Message))
            {
                var byteMsg = System.Text.Encoding.Default.GetBytes(authResult.Failure.Message);
                Response.Body.Write(byteMsg, 0, byteMsg.Length);
            }

            await base.HandleChallengeAsync(properties);
        }
    }
}
